package com.siashan.unipay4j.ali.pay;

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.siashan.unipay4j.ali.AbstractAliPay;
import com.siashan.unipay4j.core.IPayConfig;
import com.siashan.unipay4j.core.PayChannel;
import com.siashan.unipay4j.core.RefundStateEnum;
import com.siashan.unipay4j.core.Result;
import com.siashan.unipay4j.core.exception.Unipay4jOperationNotSupportedException;
import com.siashan.unipay4j.core.pay.IRefund;
import com.siashan.unipay4j.core.pay.RefundModel;
import com.siashan.unipay4j.core.pay.RefundQueryModel;
import com.siashan.unipay4j.core.pay.RefundResponse;
import com.siashan.unipay4j.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 支付宝支付：退款
 * <p>退款</p>
 * 当交易发生之后一段时间内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付款退还给买家，支付宝将在收到退款请求并且验证成功之后，
 * 按照退款规则将支付款按原路退到买家帐号上。 交易超过约定时间（签约时设置的可退款时间）的订单无法进行退款 支付宝退款支持单笔交易分多次退款，
 * 多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。一笔退款失败后重新提交，要采用原来的退款单号。
 * 总退款金额不能超过用户实际支付金额
 * <p>查询</p>
 * 商户可使用该接口查询自已通过alipay.trade.refund或alipay.trade.refund.apply提交的退款请求是否执行成功。
 * 该接口的返回码10000，仅代表本次查询操作成功，不代表退款成功。如果该接口返回了查询数据，且refund_status为空或为REFUND_SUCCESS，则代表退款成功，
 * 如果没有查询到则代表未退款成功，可以调用退款接口进行重试。重试时请务必保证退款请求号一致。
 *
 * @author siashan
 * @since v1.0.1
 **/
@Slf4j
public class AliPayRefund extends AbstractAliPay implements IRefund {


    /**
     * 资金发生变动
     */
    private static final String CHANGE_Y = "Y";

    public AliPayRefund(IPayConfig aliPaySet) {
        super(aliPaySet);
    }

    public AliPayRefund() {
        super();
    }

    /**
     * 退款请求
     *
     * 退款同步响应数据判断
     * （1）统一收单交易退款接口（alipay.trade.refund）调用成功即alipay_trade_refund_response返回"code":"10000","msg":"Success"，仅表示接口调用成功，不代表退款是否成功。
     *
     * （2）退款是否成功可以根据同步响应的fund_change参数来判断，fund_change表示本次退款是否发生了资金变化，返回“Y”表示退款成功，返回“N”则表示本次退款未发生资金变动 。
     *
     * （3）fund_change（本次退款是否发生了资金变化）是指本次接口请求是否资金变动，不是指该笔交易是否资金变动。如果第一次退款成功，返回“Y”，但是相同参数（如out_request_no不变）则会返回“N”。
     *
     * 商家如果退款成功，但未保存是否响应参数，建议使用退款查询接口进行判断，而不是使用相同参数重新请求判断。
     *
     * （4）退款接口返回fund_change=N不代表交易没有退款，只是代表该次接口请求没有资金变动，此时需使用退款查询接口进行查询判断，该笔交易是退款失败，还是退款成功后重复操作导致。
     *
     * @param model  请求实体
     * @return 请求结果
     */
    @Override
    public Result<RefundResponse> execRefund(RefundModel model) {
        log.info("AliRefund::=>退款请求到达,退款单号{}", model.getOutRefundNo());
        AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
        alipayRequest.setBizContent(JSON.toJSONString(wrapRefundParam(model)));
        try {
            AlipayTradeRefundResponse r = alipayClient.execute(alipayRequest);
            if (r.isSuccess()) {
                log.info("AliRefund::=>退款请求成功,退款单号{}", model.getOutRefundNo());
                return Result.tradeSuccess(wrapRefundResponse(r));
            } else {
                log.info("AliRefund::=>退款请求失败,退款单号{},错误代码{}，错误原因{}", model.getOutRefundNo(), r.getSubCode(), r.getSubMsg());
                return Result.tradeFail(r.getSubCode(), r.getSubMsg());
            }
        } catch (AlipayApiException e) {
            log.warn("AliRefund::=>退款请求异常,退款单号{}", model.getOutRefundNo(), e);
        }
        return Result.exception();
    }

    /**
     * 退款查询
     *
     * 商户可使用该接口查询自已通过alipay.trade.refund或alipay.trade.refund.apply提交的退款请求是否执行成功。 该接口的返回码10000，仅代表本次查询操作成功，不代表退款成功。
     * 1、如果退款请求通过同步接口(alipay.trade.refund)发起，则如果退款查询接口返回了业务信息（trade_no、out_request_no和refund_amount不为空），表示退款成功；否则表示退款没有执行成功；
     * 2、如果退款请求通过异步接口(alipay.trade.refund.apply)发起，则如果退款查询接口返回了业务信息，且refund_status为空或为REFUND_SUCCESS表示退款成功；否则表示退款没有受理成功或还没处理成功；
     * 如果退款未成功，商户可以调用退款接口重试，重试时请务必保证退款请求号和退款金额一致。 注：发起退款查询接口的时间不能离退款请求时间太短，建议之间间隔10秒以上。
     *
     * @param model  查询参数
     * @return 查询结果
     */
    @Override
    public Result<RefundResponse> execQuery(RefundQueryModel model) {
        log.info("AliRefund::=>退款查询请求到达,退款单号{}", model.getOutRefundNo());
        AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
        Map<String, Object> requestParam = new HashMap<>(2);
        requestParam.put("out_request_no", model.getOutRefundNo());
        request.setBizContent(JSON.toJSONString(requestParam));
        try {
            AlipayTradeFastpayRefundQueryResponse r = alipayClient.execute(request);
            if (r.isSuccess()) {
                log.info("AliRefund::=>退款查询请求成功,退款单号{}", model.getOutRefundNo());
                return Result.tradeSuccess(wrapRefundResponse(r));
            } else {
                log.info("AliRefund::=>退款查询请求失败,退款单号{},错误代码{}，错误原因{}", model.getOutRefundNo(), r.getSubCode(), r.getSubMsg());
                return Result.tradeFail(r.getSubCode(), r.getSubMsg());
            }
        } catch (AlipayApiException e) {
            log.warn("AliRefund::=>退款查询请求异常,退款单号{}", model.getOutRefundNo(), e);
        }

        return Result.exception();
    }

    /**
     * 退款结果通知
     *
     * @param request  request请求
     * @return 通知结果
     */
    @Override
    public Result<RefundResponse> execNotify(HttpServletRequest request) {
        throw new Unipay4jOperationNotSupportedException("支付宝支付不支持退款接口通知接口");
    }

    /**
     * 回调成功，同步返回给支付平台的报文
     *
     * @return 成功报文
     */
    @Override
    public String successMsg() {
        return null;
    }

    /**
     * 回调失败，同步返回给支付平台的报文
     *
     * @return 失败报文
     */
    @Override
    public String failMsg() {
        return null;
    }


    /**
     * 请求参数封装
     * 当前请求参数采用Map形式封装，只封装了基本的必填请求参数
     * 后期如果现有参数不满足业务场景，再考虑Java Bean 封装形式
     *
     * @param model  请求实体
     * @return 请求参数
     */
    private Map<String, Object> wrapRefundParam(RefundModel model) {
        Map<String, Object> requestParam = new HashMap<>(8);
        requestParam.put("out_trade_no", model.getOutTradeNo());
        requestParam.put("trade_no", model.getTradeNo());
        requestParam.put("refund_amount", model.getRefundAmount());
        requestParam.put("refund_reason", model.getRefundReason());
        requestParam.put("out_request_no", model.getOutRefundNo());
        return requestParam;
    }

    /**
     * 封装RefundResponse返回
     *
     * @param r AlipayTradeRefundResponse
     * @return RefundResponse
     */
    private RefundResponse wrapRefundResponse(AlipayTradeRefundResponse r) {
        RefundResponse refundResponse = new RefundResponse();
        refundResponse.setPayChannel(PayChannel.ALI);
        String fundChange = r.getFundChange();
        boolean isSuccess = CHANGE_Y.equals(fundChange);
        refundResponse.setNeedQuery(!isSuccess);
        refundResponse.setRefundState(isSuccess ? RefundStateEnum.SUCCESS : RefundStateEnum.PROCESSING);
        refundResponse.setOutRefundNo(r.getOutTradeNo());
        refundResponse.setRefundNo(r.getTradeNo());
        return refundResponse;
    }

    /**
     * 封装RefundResponse返回
     *
     * @param r AlipayTradeFastpayRefundQueryResponse
     * @return RefundResponse
     */
    private RefundResponse wrapRefundResponse(AlipayTradeFastpayRefundQueryResponse r) {
        RefundResponse refundResponse = new RefundResponse();
        refundResponse.setPayChannel(PayChannel.ALI);
        boolean isSuccess = StrUtil.isNotEmpty(r.getTradeNo()) && StrUtil.isNotEmpty(r.getOutRequestNo()) && StrUtil.isNotEmpty(r.getRefundAmount());
        refundResponse.setNeedQuery(false);
        refundResponse.setRefundState(isSuccess ? RefundStateEnum.SUCCESS : RefundStateEnum.FAIL);
        refundResponse.setOutRefundNo(r.getOutTradeNo());
        refundResponse.setRefundNo(r.getTradeNo());
        return refundResponse;
    }
}
